package com.qingzhuge.manager.service.impl;


import com.google.common.collect.Lists;
import com.qingzhuge.cache.support.CacheObject;
import com.qingzhuge.common.Assert;
import com.qingzhuge.common.HttpCode;
import com.qingzhuge.common.Resources;
import com.qingzhuge.common.enums.ERoleType;
import com.qingzhuge.common.utils.WebUtil;
import com.qingzhuge.exception.*;
import com.qingzhuge.interfaces.ISysUser;
import com.qingzhuge.manager.dao.SysUserDao;
import com.qingzhuge.manager.dto.AdminLoginDto;
import com.qingzhuge.manager.dto.AdminRegDto;
import com.qingzhuge.manager.dto.SecurityUser;
import com.qingzhuge.manager.entity.SysDept;
import com.qingzhuge.manager.entity.SysRole;
import com.qingzhuge.manager.entity.SysUser;
import com.qingzhuge.manager.service.ISysDeptService;
import com.qingzhuge.manager.service.ISysUserService;
import com.qingzhuge.resource.entity.SysResource;
import com.qingzhuge.resource.service.ISysResourceService;
import com.qingzhuge.service.BasePkLongServiceImpl;
import com.qingzhuge.sms.service.ISysSmsService;
import com.qingzhuge.sys.entity.SysSiteConfig;
import com.qingzhuge.sys.service.ISysConfigService;
import com.qingzhuge.utils.PageUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.*;

import static com.qingzhuge.common.Constants.I18n.LOGIN.PASSWORD_LENGTH;
import static com.qingzhuge.common.Constants.I18n.LOGIN.USERNAME_IS_NULL;
import static com.qingzhuge.common.Constants.ROLETYPE.ROOT;
import static com.qingzhuge.manager.service.impl.SysUserServiceImpl.CACHE_NAME;

/**
 * @author : zero.xiao
 * @description : 系统用户
 * @date :2018/6/8 0008 下午 15:09
 * @modified :
 * @since : 1.0.0
 */
@CacheConfig(cacheNames = CACHE_NAME)
@Service
public class SysUserServiceImpl extends BasePkLongServiceImpl<SysUserDao, SysUser> implements ISysUserService {
    static final String CACHE_NAME = "sysUserService";
    private static final String USER_PERMISSION = ":userPermission";
    private static final String ROLE_PERMISSION = ":rolePermission";

    @Resource
    private ISysResourceService resourceService;

    @Resource
    private ISysSmsService smsService;

    @Resource
    private ISysConfigService configService;

    @Resource
    private ISysDeptService deptService;

    @Resource
    private PasswordEncoder passwordEncoder;

    private SysSiteConfig getSite() {
        return configService.getSiteConfig();
    }

    @Override
    public String encodePassword(String password) {
        String encode = passwordEncoder.encode(password);
        log.debug("password:[{}]",encode);
        return encode;
    }

    @Override
    public boolean matchesPassword(String pawPassword, String encodePassword){
        return passwordEncoder.matches(pawPassword,encodePassword);
    }

    @Override
    public SecurityUser createUserDetails(SysUser user) {
        return new SecurityUser(
                user.getId(),
                user.getRoot(),
                user.getUsername(),
                user.getPassword(),
                user.getAvatar(),
                user.getEmail(),
                user.getMobile(),
                user.getDeptId(),
                getGrantedAuthorities(user),
                user.getCreateTime(),
                true,
                true,
                true,
                user.getStatus() != null && user.getStatus() > 0
        );
    }

    @Cacheable(key = "'loadPermissionByUserId:' + #user.id")
    public Collection<GrantedAuthority> getGrantedAuthorities(SysUser user) {
        List<GrantedAuthority> authorities = new ArrayList<>();
        if (user != null) {
            List<SysRole> roles = user.getRoles();
            if (roles != null) {
                roles.forEach(role->{
                    Boolean root = role.getRoot();
                    if (null != root && root){
                        authorities.add(new SimpleGrantedAuthority(ROOT));
                    }
                    String permission = role.getPermission();
                    if (StringUtils.isNotBlank(permission)){
                        Arrays.stream(permission.split(",")).forEach(s -> authorities.add(new SimpleGrantedAuthority(s.trim())));
                        authorities.add(new SimpleGrantedAuthority(permission));
                    }
                    ERoleType type = role.getType();
                    if (null != type){
                        authorities.add(new SimpleGrantedAuthority(type.name()));
                    }
                    List<SysResource> resources = role.getResources();
                    resources.forEach(r->{
                        String authority = r.getAuthority();
                        if (StringUtils.isNotBlank(authority)) {
                            Arrays.stream(authority.split(",")).forEach(s -> authorities.add(new SimpleGrantedAuthority(s.trim())));
                        }
                    });
                });
            }
        }
        return authorities;
    }

    @Override
    @Cacheable(value = CACHE_NAME + USER_PERMISSION, key = "'permission:user:'.concat(#userId.toString())", condition = "#userId != null")
    public List<String> queryUserPermission(Long userId) {
        return resourceService.queryPermissionByUserId(userId);
    }

    @Override
    @Cacheable(value = CACHE_NAME + ROLE_PERMISSION, key = "'permission:user:'.concat(#roleId.toString())", condition = "#roleId != null")
    public List<String> queryRolePermission(Long roleId) {
        return resourceService.queryPermissionByRoleId(roleId);
    }

    @Override
    public SysUser findByUsername(String username) {
        SysUser entity = new SysUser();
        entity.setUsername(username);
        return findOne(entity);
    }

    @Override
    public SysUser findByMobile(String phone) {
        SysUser user = new SysUser();
        user.setMobile(phone);
        return findOne(user);
    }

    @Override
    public SysUser findByRoot() {
        return baseDao.findByRoot();
    }

    @Override
    public List<SysUser> findListByRoot(boolean root) {
        return baseDao.findListByRoot(root);
    }

    @Override
    public int countByRootFalse() {
        return baseDao.countByRootFalse();
    }

    @Override
    public SysUser findByUsernameOrPhone(String account) {
        return baseDao.findByUsernameOrMobile(account, account);
    }

    @Override
    public PageUtil<SysUser> query(SysUser entity) {
        entity.setSearchField(ISysUser.FIELD_USERNAME);
        PageUtil<SysUser> page = super.query(entity);
        for (SysUser user : page.getContent()) {
            List<SysResource> userResources = new ArrayList<>();
            user.getRoles().forEach(role -> {
                List<SysResource> resources = role.getResources();
                userResources.addAll(resources);
            });
            userResources.forEach(res -> {
                String permission = res.getPermissions();
                if (StringUtils.isBlank(user.getPermission())) {
                    user.setPermission(permission);
                } else {
                    user.setPermission(user.getPermission() + ";" + permission);
                }
            });
        }
        return page;
    }


    @Override
    @Cacheable(condition = "#id!=null", key = "#id")
    public SysUser findById(Long id) {
        SysUser user = super.findById(id);
        if (null != user) {
            List<SysRole> roles = new ArrayList<>();
            user.getRoles().forEach(r -> {
                SysRole role = new SysRole(r.getName(), r.getId());
                role.setType(r.getType());
                if (!roles.contains(role)) {
                    roles.add(role);
                }
            });
            user.setRoles(roles);
        }
        return user;
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(key = "#user.id", condition = "#user!=null && #user.id != null")
    public SysUser save(SysUser user) {
        if (StringUtils.isBlank(user.getUsername())) {
            throw new ValidatorException(Resources.getMessage(USERNAME_IS_NULL));
        }
        SysUser dbUser = findByUsername(user.getUsername());
        if (null != dbUser) {
            //如果用户名已存在,并且传入的id为空或不相同,则为用户名重复
            if (!Objects.equals(user.getId(), dbUser.getId())) {
                throw new UserAlreadyExistsException();
            }
        }
        //手机号不能重复
        if (null == dbUser && StringUtils.isNotBlank(user.getMobile())) {
            dbUser = findByMobile(user.getMobile());
        }
        if (null != dbUser) {
            //如果用户名已存在,并且传入的id为空或不相同,则为用户名重复
            if (!Objects.equals(user.getId(), dbUser.getId())) {
                throw new UserAlreadyExistsException();
            }
        }
        if (user.isNew()) {
            user.setStatus(1);
            if (StringUtils.isBlank(user.getPassword())) {
                user.setPassword(getSite().getAdminDefPassword());
            }
            user.setPassword(encodePassword(user.getPassword()));
        } else {
            if (StringUtils.isBlank(user.getPassword())) {
                user.setPassword(null);
            } else {
                if (user.getPassword().length() < 64) {
                    user.setPassword(encodePassword(user.getPassword()));
                }
            }
        }
        return super.save(user);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updatePassword(Long uid, String password, String newPassword) {
        SysUser user = findById(uid);
        this.updatedPassword(password, newPassword, user);
    }

    @Transactional(rollbackFor = Exception.class)
    public void updatedPassword(String password, String newPassword, SysUser user) {
        if (null != user) {
            if (StringUtils.isNotBlank(password)) {
                if (!matchesPassword(password,user.getPassword())) {
                    throw new ServiceException(HttpCode.OLD_PASSWORD_ERROR.message(), HttpCode.OLD_PASSWORD_ERROR.value());
                }
            }
            String newPwd = encodePassword(newPassword);
            baseDao.updatePassword(user.getId(), newPwd);
        }
    }

    @Override
    public Collection<SysResource> queryResourceByUid(Long userId) {
        SysUser user = super.findById(userId);
        Collection<SysResource> resources = new ArrayList<>();
        if (null != user) {
            user.getRoles().forEach(r -> resources.addAll(r.getResources()));
        }
        return resources;
    }

    /**
     * 后台注册
     *
     * @param register 注册对象
     * @return 新用户
     */
    @Override
    public SysUser register(AdminRegDto register) {
        if (getSite().isAdminSmsCodeRegister()) {
            if (StringUtils.isBlank(register.getVerificationCode())) {
                throw new SmsCodeException();
            }
            String codeKey = smsService.getRegisterCodeKey(Assert.mobile(register.getMobile()));
            deleteSmsCache(register.getVerificationCode(), codeKey);
        }
        SysUser sysUser = new SysUser();
        if (StringUtils.isBlank(register.getUsername())) {
            sysUser.setUsername(register.getMobile());
        } else {
            sysUser.setUsername(register.getUsername());
        }
        sysUser.setEmail(register.getEmail());
        sysUser.setMobile(register.getMobile());
        if (StringUtils.isNotBlank(register.getPassword())) {
            if (getSite().getAdminPwdMinLength() > register.getPassword().length() || getSite().getAdminPwdMaxLength() < register.getPassword().length()) {
                throw new ValidatorException(Resources.getMessage(PASSWORD_LENGTH, getSite().getAdminPwdMinLength(), getSite().getAdminPwdMaxLength()));
            } else {
                sysUser.setPassword(register.getPassword());
            }
        }
        return this.save(sysUser);
    }

    /**
     * 后台登录
     *
     * @param login 登录对象
     * @return 登录用户
     */
    @Override
    public SysUser login(AdminLoginDto login) {

        if (getSite().isAdminKaptcha()) {
            String kaptcha = WebUtil.getKaptcha();
            if (!kaptcha.equalsIgnoreCase(login.getCaptcha())) {
                throw new KaptchaException();
            }
        }

        if (getSite().isAdminSmsCodeLogin()) {
            if (StringUtils.isBlank(login.getVerificationCode())) {
                throw new SmsCodeException();
            }
            String codeKey = smsService.getLoginCodeKey(Assert.mobile(login.getPhone()));
            deleteSmsCache(login.getVerificationCode(), codeKey);
        }

        SysUser user = null;
        if (StringUtils.isBlank(login.getUsername()) && StringUtils.isNotBlank(login.getPhone())) {
            login.setUsername(login.getPhone());
        }
        if (StringUtils.isNotBlank(login.getUsername())) {
            user = this.findByUsernameOrPhone(login.getUsername());
        }
        return user;
    }

    /**
     * 重置后台账号密码
     *
     * @param reset 登录对象
     * @return 登录用户
     */
    @Override
    public boolean resetPwd(AdminRegDto reset) {
        SysUser user = null;
        if (StringUtils.isNotBlank(reset.getUsername())) {
            user = this.findByUsernameOrPhone(reset.getUsername());
        } else if (StringUtils.isNotBlank(reset.getMobile())) {
            user = this.findByUsernameOrPhone(reset.getMobile());
        }
        if (null != user) {
            if (!Objects.equals(reset.getMobile(), user.getMobile())) {
                throw new MobileException();
            }
            if (StringUtils.isBlank(reset.getVerificationCode())) {
                throw new SmsCodeException();
            }
            String codeKey = smsService.getResetPwdCodeKey(Assert.mobile(reset.getMobile()));
            deleteSmsCache(reset.getVerificationCode(), codeKey);
            this.updatedPassword(null, reset.getPassword(), user);
            return true;
        } else {
            throw new ValidatorException(HttpCode.NOT_ACCOUNT.message(), HttpCode.NOT_ACCOUNT.value());
        }
    }

    private void deleteSmsCache(String verificationCode, String codeKey) {
        String code = smsService.getSmsCodeForCache(codeKey);
        if (!verificationCode.equalsIgnoreCase(code)) {
            throw new SmsCodeException();
        } else {
            smsService.deleteSmsCodeForCache(codeKey);
        }
    }

    @Override
    public List<SysUser> findListByDept(Long deptId) {
        List<SysUser> userList = Lists.newArrayList();
        getUserListByDept(deptId, userList);
        List<SysDept> children = deptService.getChildren(deptId);
        if (null != children && children.size() > 0) {
            children.forEach(dept -> getUserListByDept(dept.getId(), userList));
        }
        return userList;
    }

    private void getUserListByDept(Long deptId, List<SysUser> userList) {
        CacheObject<List<SysUser>> cacheObject = getCacheObject("deptId:" + deptId);
        List<SysUser> list = cacheObject.getValue();
        if (null == list){
            list = baseDao.findListByDeptId(deptId);
            saveCache("deptId:" + deptId,list);
        }
        if (null != list && list.size() > 0) {
            userList.addAll(list);
        }
    }
}


